namespace PGPlot
{
    using System;
    using System.Runtime.InteropServices;
    
    public class Plot : IDisposable
    {
        private string X_OPTIONS = "bcnst";
        private string Y_OPTIONS = "bcnstv";
        private int symbol = 0;
        private int id;
        private bool disposed = false;
        
        //
        // Opening, Closing, and Selecting Devices
        //
        
        public Plot(string device)
        {
            id = cpgopen(device);
            if (id <= 0)
                throw new ApplicationException("Can't use device " + device);
        }
        
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    // no managed resources to dispose of
                }
                if (current_id != id)
                    cpgslct(id);
                cpgclos();
            }
            disposed = true;
        }
        
        ~Plot()
        {
            Dispose(false);
        }
        
        private static int current_id
        {
            get
            {
                int i;
                cpgqid(out i);
                return i;
            }
        }
        
        public static DeviceInfo[] Devices // hmm ... doesn't work
        {
            get
            {
                int n;
                cpgqndt(out n);
                DeviceInfo[] devices = new DeviceInfo[n];
                for (int i = 0; i < n; i++)
                {
                    char[] t = new char[64];
                    char[] d = new char[64];
                    int ti, di;
                    int inter;
                    cpgqdt(i+1, t, out ti, d, out di, out inter);
                    bool test;
                    if (inter == 1) test = true;
                    else test = false;
                    devices[i] = new DeviceInfo(new String(t), new String(d), test);
                }
                
                return devices;
            }
        }
        
        //
        // Controlling the view surface
        //
        
        public void NewPage()
        {
            if (current_id != id)
                cpgslct(id);
            cpgpage();
        }
        
        public void Erase()
        {
            if (current_id != id)
                cpgslct(id);
            cpgeras();
        }
        
        public bool Ask
        {
            set
            {
                if (current_id != id)
                    cpgslct(id);
                cpgask(value);
            }
        }
        
        public void SetSurfaceSize(double width, double aspect)
        {
            if (current_id != id)
                cpgslct(id);
            cpgpap((float)width, (float)aspect);
        }
        
        public void GetSurfaceSize(Units units, out double x1, out double x2, out double y1, out double y2)
        {
            float fx1, fx2, fy1, fy2;
            if (current_id != id)
                cpgslct(id);
            cpgqvsz(units, out fx1, out fx2, out fy1, out fy2);
            x1 = fx1;
            x2 = fx2;
            y1 = fy1;
            y2 = fy2;
        }
        
        //
        // Windows and viewports
        //
        
        public void SetViewport(double xleft, double xright, double ybot, double ytop)
        {
            if (current_id != id)
                cpgslct(id);
            cpgsvp((float)xleft, (float)xright, (float)ybot, (float)ytop);
        }
        
        public void SetLimits(double x1, double x2, double y1, double y2)
        {
            if (current_id != id)
                cpgslct(id);
            cpgswin((float)x1, (float)x2, (float)y1, (float)y2);
        }
        
        public void SetLimits(double x1, double x2, double y1, double y2, bool adjust_viewport)
        {	
        	if (current_id != id)
                cpgslct(id);
            if (adjust_viewport)
            	cpgwnad((float)x1, (float)x2, (float)y1, (float)y2);
            else
            	cpgswin((float)x1, (float)x2, (float)y1, (float)y2);
        }        
        
        //
        // Lines
        //
        
        public void Move(double x, double y)
        {
            if (current_id != id)
                cpgslct(id);
            cpgmove((float)x, (float)y);
        }
        
        public void Draw(double x, double y)
        {
            if (current_id != id)
                cpgslct(id);
            cpgdraw((float)x, (float)y);
        }
        
        public void Line(double[] x, double[] y)
        {
            int n = x.Length;
            if (y.Length < n)
                n = y.Length;
            
            // convert to floats, yuck
            float[] nx = new float[n];
            float[] ny = new float[n];
            for (int i = 0; i < n; i++)
            {
                nx[i] = (float)x[i];
                ny[i] = (float)y[i];
            }
            
            if (current_id != id)
                cpgslct(id);
            cpgline(n, nx, ny);
        }
        
        public void Line(float[] x, float[] y)
        {
            int n = x.Length;
            if (y.Length < n)
                n = y.Length;            
            
            if (current_id != id)
                cpgslct(id);
            cpgline(n, x, y);
        }
        
        //
        // Graph Markers
        //
        
        public void Points(double[] x, double[] y, int symbol)
        {
            int n = x.Length;
            if (y.Length < n)
                n = y.Length;
            
            // convert to floats, yuck
            float[] nx = new float[n];
            float[] ny = new float[n];
            for (int i = 0; i < n; i++)
            {
                nx[i] = (float)x[i];
                ny[i] = (float)y[i];
            }
            
            if (current_id != id)
                cpgslct(id);
            cpgpt(n, nx, ny, symbol);
        }
        
        public void Points(double[] x, double[] y)
        {
            Points(x, y, symbol);
        }
        
        public void Points(float[] x, float[] y, int symbol)
        {
            int n = x.Length;
            if (y.Length < n)
                n = y.Length;
            
            if (current_id != id)
                cpgslct(id);
            cpgpt(n, x, y, symbol);
        }
        
        public void Points(float[] x, float[] y)
        {
           Points(x, y, symbol);
        }
        
        public void Point(double x, double y, int symbol)
        {
            float fx = (float)x;
            float fy = (float)y;
            cpgpt1(fx, fy, symbol);
        }
        
        public int Symbol
        {
            set
            {
                symbol = value;
            }
        }
        
        //
        // Text
        //
        
        public void Text(double x, double y, string text)
        {
            cpgtext((float)x, (float)y, text);
        }
        
        public void MText(string side, double disp, double coord, double fjust, string text)
        {
            cpgmtxt(side, (float)disp, (float)coord, (float)fjust, text);
        }
        
        //
        // Axes, Boxes, and Labels
        //
        
        public void Box(string xopt, double xtick, int nxsub, string yopt, double ytick, int nysub)
        {
            if (current_id != id)
                cpgslct(id);
            cpgbox(xopt, (float)xtick, nxsub, yopt, (float)ytick, nysub);
        }
        
        public void Box(string xopt, string yopt)
        {
            if (current_id != id)
                cpgslct(id);
            cpgbox(xopt, 0, 0, yopt, 0, 0);
        }
        
        public void Box()
        {
            if (current_id != id)
                cpgslct(id);
            cpgbox(X_OPTIONS, 0, 0, Y_OPTIONS, 0, 0);
        }
        
        public void LogBox() // should I keep this around?
        {
            if (current_id != id)
                cpgslct(id);
            cpgbox(X_OPTIONS + "l", 0, 0, Y_OPTIONS + "l", 0, 0);
        }
        
        public void Label(string xlabel, string ylabel, string toplabel)
        {
            if (current_id != id)
                cpgslct(id);
            cpglab(xlabel, ylabel, toplabel);
        }
        
        public void XLabel(string text)
        {
            if (current_id != id)
                cpgslct(id);
            cpgmtxt("B", 3.0f, 0.5f, 0.5f, text);
        }
        
        public void YLabel(string text)
        {
            if (current_id != id)
                cpgslct(id);
            cpgmtxt("L", 3.5f, 0.5f, 0.5f, text);
        }
        
        //
        // Misc
        //
        
        public void BufferBegin()
        {
            cpgbbuf();
        }
        
        public void BufferEnd()
        {
            cpgebuf();
        }
        
        //
        // Drawing stuff
        //
        
        public void DrawCircle(double xcent, double ycent, double radius)
        {
            cpgcirc((float)xcent, (float)ycent, (float)radius);
        }
        
        //
        // Attributes
        //
        
        public int Color
        {
            set { cpgsci(value); }
        }
        
        public void SetColorIndex(int index, string name)
        {
            int ret;
            cpgscrn(index, name, out ret);
            if (ret != 0)
                throw new ApplicationException("Can't find color " + name);
        }
        
        public void SetColorIndex(int index, double r, double g, double b)
        {
            cpgscr(index, (float)r, (float)g, (float)b);
        }
        
        public LineStyle LineStyle
        {
            get { LineStyle ls;  cpgqls(out ls); return ls; }
            set { cpgsls(value); }
        }
        
        public int LineWidth
        {
            get { int lw;  cpgqlw(out lw); return lw; }
            set { cpgslw(value); }
        }
        
        public Font Font
        {
            get { Font f;  cpgqcf(out f); return f; }
            set { cpgscf(value); }
        }
        
        public float FontSize
        {
            get { float fs;  cpgqch(out fs); return fs; }
            set { cpgsch(value); }
        }
        
        public FillAreaStyle FillAreaStyle
        {
            get { FillAreaStyle fs;  cpgqfs(out fs); return fs; }
            set { cpgsfs(value); }
        }
            
        // Opening, Closing, and Selecting Devices
        [DllImport("cpgplot")] private static extern int cpgopen(string device);
        [DllImport("cpgplot")] private static extern int cpgclos();
        [DllImport("cpgplot")] private static extern int cpgslct(int id);
        [DllImport("cpgplot")] private static extern void cpgqid(out int id);
        
        [DllImport("cpgplot")] private static extern void cpgqndt(out int n);
        [DllImport("cpgplot")] private static extern void cpgqdt(int n, char[] type, out int type_length, char[] desc, out int descr_length, out int inter);
        
        // Controlling the view surface
        [DllImport("cpgplot")] private static extern void cpgpage();
        [DllImport("cpgplot")] private static extern void cpgeras();
        [DllImport("cpgplot")] private static extern void cpgask(bool flag);
        
        [DllImport("cpgplot")] private static extern void cpgpap(float width, float aspect);
        [DllImport("cpgplot")] private static extern void cpgqvsz(Units units, out float x1, out float x2, out float y1, out float y2);
                
        // Windows and viewports
        [DllImport("cpgplot")] private static extern void cpgsvp(float xleft, float xright, float ybot, float ytop);
        [DllImport("cpgplot")] private static extern void cpgswin(float x1, float x2, float y1, float y2);
        [DllImport("cpgplot")] private static extern void cpgwnad(float x1, float x2, float y1, float y2);
        
        // Lines
        [DllImport("cpgplot")] private static extern void cpgmove(float x, float y);
        [DllImport("cpgplot")] private static extern void cpgdraw(float x, float y);
        [DllImport("cpgplot")] private static extern void cpgline(int n, float[] xpts, float[] ypts);
        
        // Graph markers
        [DllImport("cpgplot")] private static extern void cpgpt(int n, float[] xpts, float[] ypts, int symbol);
        [DllImport("cpgplot")] private static extern void cpgpt1(float xpt, float ypt, int symbol);
        
        // Text
        [DllImport("cpgplot")] private static extern void cpgtext(float x, float y, string text);
        [DllImport("cpgplot")] private static extern void cpgmtxt(string side, float disp, float coord, float fjust, string text);
        
        // Axes, Boxes, and Labels
        [DllImport("cpgplot")] private static extern void cpgbox(string xopt, float xtick, int nxsub, string yopt, float ytick, int nysub);        
        [DllImport("cpgplot")] private static extern void cpglab(string xlbl, string ylbl, string toplbl);
        
        // Misc
        [DllImport("cpgplot")] private static extern void cpgbbuf();
        [DllImport("cpgplot")] private static extern void cpgebuf();
        
        // drawing stuff
        [DllImport("cpgplot")] private static extern void cpgcirc(float xc, float yc, float rad);
        
        // Attributes - color
        [DllImport("cpgplot")] private static extern void cpgsci(int c);
        [DllImport("cpgplot")] private static extern void cpgscr(int ci, float cr, float cg, float cb);
        [DllImport("cpgplot")] private static extern void cpgscrn(int ci, string name, out int ret);
        
        
        // Attributes - lines
        [DllImport("cpgplot")] private static extern void cpgsls(LineStyle ls);
        [DllImport("cpgplot")] private static extern void cpgslw(int lw);
        [DllImport("cpgplot")] private static extern void cpgqls(out LineStyle ls);
        [DllImport("cpgplot")] private static extern void cpgqlw(out int lw);
        
        // Attributes - text
        [DllImport("cpgplot")] private static extern void cpgscf(Font font);
        [DllImport("cpgplot")] private static extern void cpgqcf(out Font font);
        [DllImport("cpgplot")] private static extern void cpgsch(float size);
        [DllImport("cpgplot")] private static extern void cpgqch(out float size);
        
        // Attributes - other
        [DllImport("cpgplot")] private static extern void cpgsfs(FillAreaStyle fs);
        [DllImport("cpgplot")] private static extern void cpgqfs(out FillAreaStyle fs);
        
        /*public static void Main(string[] args)
        {    
            Plot plot = new Plot("/xs");
            plot.SetViewport(0.1, 0.9, 0.1, 0.9);
            plot.SetLimits(-1, 11, -10, 110);
            int n = 10;
            double[] x = new double[n];
            double[] y = new double[n];
            for (int i = 0; i < n; i++)
            {
                x[i] = i;
                y[i] = x[i] * x[i];
            }
            
            plot.Line(x, y);
            plot.Points(x, y, 17);
            plot.Box();
            
            double x1, x2, y1, y2;
            plot.GetSurfaceSize(Units.Normalized, out x1, out x2, out y1, out y2);
            Console.WriteLine("Size: {0} {1} {2} {3}", x1, x2, y1, y2);
        }*/
    }
}
